/**
 * reference https://github.com/James-Smyth/three-grass-demo
 */
import { GLTFLoader } from "three/addons/loaders/GLTFLoader.js";
import { DRACOLoader } from "three/addons/loaders/DRACOLoader.js";
import {
  Vector3,
  TextureLoader,
  Group,
  Clock,
  MeshBasicMaterial,
  ShaderMaterial,
  Mesh,
  RepeatWrapping,
  DoubleSide,
  AdditiveBlending,
  CylinderGeometry,
  Matrix4,
  MultiplyBlending,
  Vector2,
  PerspectiveCamera,
  WebGLRenderer,
  Scene,
  MeshStandardMaterial,
  BoxGeometry,
  DirectionalLight,
  BufferGeometry,
  Float32BufferAttribute,
  Material,
  PlaneGeometry,
  Color,
  Texture,
  InstancedMesh,
  InstancedBufferGeometry,
  InterleavedBuffer,
  InterleavedBufferAttribute,
  InstancedInterleavedBuffer,
  Box3,
  Sphere,
  Quaternion,
  Int16BufferAttribute,
  Int32BufferAttribute,
  Points,
  PointsMaterial,
} from "three";
import { OrbitControls } from "three/examples/jsm/controls/OrbitControls.js";
import GUI from "three/examples/jsm/libs/lil-gui.module.min.js";
import { canvasToImage } from "../utils/image";
const gltfLoader = new GLTFLoader();
const dracoLoader = new DRACOLoader();
dracoLoader.setDecoderPath("/draco/");
gltfLoader.setDRACOLoader(dracoLoader);

const BLADE_WIDTH = 0.1;
const BLADE_HEIGHT = 0.8;
const BLADE_HEIGHT_VARIATION = 0.6;
const BLADE_TIP_OFFSET = 0.1;

const { random, sin, cos, tan, min, max, PI } = Math;

const BLADE_VERTEX_COUNT = 5; //这个除非形状变了，是真正的常量
/**
 * 线性插值函数
 * @param val - 原始值
 * @param oldMin - 原始范围的最小值
 * @param oldMax - 原始范围的最大值
 * @param newMin - 新范围的最小值
 * @param newMax - 新范围的最大值
 * @returns 返回在新区间内对应的值
 */
function interpolate(val, oldMin, oldMax, newMin, newMax) {
  return ((val - oldMin) * (newMax - newMin)) / (oldMax - oldMin) + newMin;
}
function initGeometry(count = 100, size = 10) {
  const geometry = new InstancedBufferGeometry();
  // positions: [...bl, ...br, ...tr, ...tl, ...tc],
  const indices = [
    -BLADE_WIDTH,
    0,
    BLADE_WIDTH,
    0,
    BLADE_WIDTH * 0.5,
    BLADE_HEIGHT * 0.5,
    -BLADE_WIDTH * 0.5,
    BLADE_HEIGHT * 0.5,
    0,
    BLADE_HEIGHT,
  ];
  const uvs = [0, 0, 1, 0, 0.75, 0.5, 0.25, 0.5, 0.5, 1];
  const cloudUvs = [];
  const vertices = [0, 1, 2, 2, 4, 3, 3, 0, 2];
  const vertexBuffer = new Float32BufferAttribute(vertices, 2);
  const uvBuffer = new Float32BufferAttribute(uvs, 2);
  // const cloudUvBuffer = new Float32BufferAttribute(cloudUvs,2);
  const dataView = new Float32Array(count * 5 * 1);
  geometry.setAttribute("position", vertexBuffer);
  geometry.setAttribute("uv", uvBuffer);
  geometry.setIndex(indices);

  /** 接下来就是把之前的逻辑 改成填充这些数组 以及matrix */
  const matrixs = [],
    mat4 = new Matrix4(),
    v1 = new Vector3(),
    v2 = new Vector3(),
    v3 = new Vector3(),
    q1 = new Quaternion();

  const surfaceMin = size * -0.5;
  const surfaceMax = size * 0.5;
  let x, y, z, u, v;
  for (let i = 0; i < count; i++) {
    const radius = surfaceMax * random();
    const theta = random() * PI * 2;
    x = radius * cos(theta);
    y = radius * sin(theta);
    v1.set(x, 0, y);
    //  其实可以把高度的变化，放在缩放这里
    const scaleY = 1 + (random() * BLADE_HEIGHT_VARIATION) / BLADE_HEIGHT;
    v2.set(1, scaleY, 1);
    mat4.scale(v1);
    mat4.compose(v1, q1, v2);
    matrixs.push(...mat4.toArray());

    /**这样 每片叶子的uv都是同一个值，也就是说采样的话，一片叶子采到一个颜色
     * 这个算法可以说和目前点位的分布逻辑强相关， 当然，如果我想搞一个点分布于面上，也可以计算包围盒
     */
    //  u = interpolate(x,surfaceMin,surfaceMax,0,1);
    //  v = interpolate(y,surfaceMin,surfaceMax,0,1);
    //  dataView.set([u,v],i * 10 + 2);
    const yaw = random() * PI * 2;
    const bend = random() * PI * 2;
    dataView.set([yaw, yaw, yaw, yaw, bend], i * 5);
  }
  const instancedBuffer = new InstancedInterleavedBuffer(dataView, 1, 1);
  // 初始的随机旋转也在着色器里计算。
  // 一个顶点一个角度，只需要一个buffer
  const yawBuffer = new InterleavedBufferAttribute(instancedBuffer, 1, 0);
  const cloudUvBuffer = new InterleavedBufferAttribute(instancedBuffer, 2, 1);
  geometry.setAttribute("yaw", yawBuffer);
  //  uvcloud可以不要
  //  二维的数据，它自动计算就不太行。
  const box = new Box3(
    new Vector3(-size, 0, -size),
    new Vector3(size, BLADE_HEIGHT + BLADE_HEIGHT_VARIATION, size)
  );
  const sphere = new Sphere(new Vector3(), size);
  geometry.boundingBox = box;
  geometry.boundingSphere = sphere;
  return { geometry, matrixs };
}
class InstancedGrassMaterial extends ShaderMaterial {
  uniforms = {
    uTime: { value: 0 },
    uOffset: { value: [0.5, 0.3] },
    uRange: { value: [10, 10] },
    uCloud: { value: null },
    textures: { value: [null, null] },
    grassColor: { value: [0.1, 0.99, 0.5] },
    uWaveDir: { value: new Vector2(1, 0) }, // 风向， 只考虑x轴，y轴就是0， 因为是xz平
  };
  side = DoubleSide;
  vertexShader = /*glsl */ `
   uniform float uTime;
   uniform vec2 uOffset;
   uniform vec2 uWaveDir;
   uniform vec2 uRange;
   uniform sampler2D uCloud;
   uniform sampler2D textures[2];
   uniform vec3 grassColor ;
   varying vec3 vGrassColor ;
  //  attribute vec2 uv;
  //  attribute float yaw;
   varying vec2 vUv;
  //  varying vec3 vNormal;
   varying vec3 vPosition;
   varying vec3 vCloudColor ;
   attribute mat4 instanceMatrix;
  //  虽然我只有5个点，这里也可以改成真正的bend
   float wave(float waveSize , float tipDistance,float centerDistance,bool isTip){
   
     float waveDist = isTip ? tipDistance : centerDistance;
     return sin(uTime* 0.002 + waveSize)* waveDist;  
    }
    float random (vec2 st) {
      return fract(sin(dot(st.xy, vec2(12.9898,78.233)))* 43758.5453123);
    }
    mat3 bend (float angle){
      float c = cos(angle);
      float s = sin(angle);
      return mat3(
        c,0,-s,
        0,1,0,
        s,0,c);
    }
    vec3 bend (float angle, in vec3 pos ){
      float c = cos(angle);
      float s = sin(angle);
      pos.xz+= vec2(s,-c)*pos.x;
      return pos;
    }
    void main() {
      float BLADE_TIP_OFFSET= 0.1;
      bool isTip  = gl_VertexID % 5  ==  4;// 现在也可以用uv来判断
      vUv = uv;
      vec3 pos = instanceMatrix[3].xyz; 
      vec2 cloudUv = pos.xz / uRange ;
      vec2 vCloudUv= cloudUv  +0.5;
      // vNormal = normal;
      vPosition = position;
     if( isTip ){
       vPosition.x += BLADE_TIP_OFFSET;
     } 
     vPosition.xyz =  bend((random(pos.xz)))* vPosition.xyz;
    //  bend(yaw,vPosition);

    //  vNormal = normalize(normalMatrix * normal);
    //  理论上 y肯定不会小于0 
    // 这个幅度只和uv.x相关，另外搞这个关联，主要是为了避免单调吧，因为现在风向就是x轴  风向可以考虑参数化
      vec2 dir  = uWaveDir* cloudUv.x ;
      float fact  =  abs(dir.x) + abs(dir.y);
      vPosition.xy += wave(5. * fact ,0.3,0.1,isTip)*uWaveDir;
     // 优化 这个渐变色可以在顶点着色器中计算。
     vGrassColor= grassColor* uv.y* texture2D(textures[1],vCloudUv).rgb*1.5;
     vCloudUv+= uOffset * uTime * vec2(0.0002,0.0004);
     vCloudColor =  texture2D(textures[0],vCloudUv).rgb;
     gl_Position = projectionMatrix * viewMatrix * instanceMatrix * vec4(vPosition, 1.0);
   }
   `;
  fragmentShader = /*glsl */ `
   uniform float uTime;
   uniform vec2 uOffset;
   uniform sampler2D uCloud;
   varying vec2 vUv;
  //  varying vec3 vNormal;
   varying vec3 vPosition;
   varying vec3 vGrassColor ;
   varying vec3 vCloudColor ;
   void main() {
     // 光照计算
     vec3 lightDirection = vec3(0.6);
    //  float lightness = abs(dot(vNormal, lightDirection))+0.1; 
     gl_FragColor = vec4(mix(vGrassColor,vCloudColor,.4) ,1.);
    vec3 uvColor = vec3(vUv.x,0.5,vUv.y);
   }
   `;
  constructor(size = 1) {
    super();
    this.uniforms.uRange.value = [2 * size, 2 * size];
  }
}
class InstancedGrass extends InstancedMesh {
  declare material: InstancedGrassMaterial;
  constructor(size = 1, count = 100) {
    const { matrixs, geometry } = initGeometry(count, size);
    const mat = new InstancedGrassMaterial(size);
    super(geometry, mat, count);
    this.instanceMatrix.set(matrixs);
    this.instanceMatrix.needsUpdate = true;
  }
  update(time) {
    this.material.uniforms.uTime.value = time;
  }
}
class GrassGeometry extends BufferGeometry {
  constructor(size = 1, count = 100) {
    super();
    const vertices = [];
    const uvs = [];
    const cloudUvs = [];
    const indices = [];

    const surfaceMin = size * -0.5;
    const surfaceMax = size * 0.5;
    let x, y, z, u, v;
    for (let i = 0; i < count; i++) {
      const radius = surfaceMax * random();
      const theta = random() * PI * 2;
      x = radius * cos(theta);
      y = radius * sin(theta);
      /**这样 每片叶子的uv都是同一个值，也就是说采样的话，一片叶子采到一个颜色
       * 这个算法可以说和目前点位的分布逻辑强相关， 当然，如果我想搞一个点分布于面上，也可以计算包围盒
       */
      u = interpolate(x, surfaceMin, surfaceMax, 0, 1);
      v = interpolate(y, surfaceMin, surfaceMax, 0, 1);
      for (let index = 0; index < BLADE_VERTEX_COUNT; index++) {
        cloudUvs.push(u, v);
      }
      // positions: [...bl, ...br, ...tr, ...tl, ...tc],

      uvs.push(0, 0, 1, 0, 0.75, 0.5, 0.25, 0.5, 0.5, 1);

      const blade = this.computeBlade([x, 0, y], i);
      vertices.push(...blade.positions);
      indices.push(...blade.indices);
    }
    this.setIndex(indices);
    this.setAttribute("position", new Float32BufferAttribute(vertices, 3));
    this.setAttribute("uv", new Float32BufferAttribute(uvs, 2));
    this.setAttribute("cloudUv", new Float32BufferAttribute(cloudUvs, 2));
    this.computeVertexNormals();
  }
  // Grass blade generation, covered in https://smythdesign.com/blog/stylized-grass-webgl
  // TODO: reduce vertex count, optimize & possibly move to GPU
  /**
   * 五点三角形，分上面的三角形和下面的梯形（两个三角形）
   * @param center
   * @param index
   * @returns
   */
  computeBlade(center, index = 0) {
    const height = BLADE_HEIGHT + Math.random() * BLADE_HEIGHT_VARIATION; //固定+ 随机 高度
    const vIndex = index * BLADE_VERTEX_COUNT;

    // Randomize blade orientation and tip angle 都是y轴的偏转，  尖端的点就在中心所以加了一个偏移量，等若半径，梯形那四点的偏转就是正常的
    const yaw = Math.random() * Math.PI * 2;
    const yawVec = [Math.sin(yaw), 0, -Math.cos(yaw)];
    const bend = Math.random() * Math.PI * 2;
    const bendVec = [Math.sin(bend), 0, -Math.cos(bend)];

    // Calc bottom, middle, and tip vertices
    const bl = yawVec.map((n, i) => n * (BLADE_WIDTH / 2) * 1 + center[i]);
    const br = yawVec.map((n, i) => n * (BLADE_WIDTH / 2) * -1 + center[i]);
    const tl = yawVec.map((n, i) => n * (BLADE_WIDTH / 4) * 1 + center[i]);
    const tr = yawVec.map((n, i) => n * (BLADE_WIDTH / 4) * -1 + center[i]);
    const tc = bendVec.map((n, i) => n * BLADE_TIP_OFFSET + center[i]);

    // Attenuate height
    tl[1] += height / 2;
    tr[1] += height / 2;
    tc[1] += height;

    return {
      positions: [...bl, ...br, ...tr, ...tl, ...tc],
      indices: [
        vIndex,
        vIndex + 1,
        vIndex + 2,
        vIndex + 2,
        vIndex + 4,
        vIndex + 3,
        vIndex + 3,
        vIndex,
        vIndex + 2,
      ],
    };
  }
}
class GrassMaterial extends ShaderMaterial {
  uniforms = {
    uTime: { value: 0 },
    uOffset: { value: [0.5, 0.3] },
    uRange: { value: [10, 10] },
    uCloud: { value: null },
    textures: { value: [null, null] },
    grassColor: { value: [0.1, 0.99, 0.5] },
    uWaveDir: { value: new Vector2(1, 0) }, // 风向， 只考虑x轴，y轴就是0， 因为是xz平
  };
  side = DoubleSide;
  vertexShader = /*glsl */ `
   uniform float uTime;
   uniform vec2 uOffset;
   uniform vec2 uRange;
   uniform vec2 uWaveDir;
   uniform sampler2D uCloud;
   uniform sampler2D textures[2];
   uniform vec3 grassColor ;
   varying vec3 vGrassColor ;
   varying vec3 vCloudColor ;
   varying vec2 vUv;
   attribute vec2 cloudUv;
   varying vec3 vNormal;
   varying vec3 vPosition;
   
   float wave(float waveSize , float tipDistance,float centerDistance){
     bool isTip  = gl_VertexID % 5  ==  4;// 现在也可以用uv来判断
     float waveDist = isTip ? tipDistance : centerDistance;
     return sin(uTime* 0.002 + waveSize)* waveDist;  
    }
    void main() {
      vUv = uv;
      vec2 vCloudUv= cloudUv ;
      // vec2 cloudUv = position.xz / uRange ;
     vCloudUv+= uOffset * uTime * vec2(0.0002,0.0004);
     vNormal = normal;
     vPosition = position; 
     vCloudUv+= uOffset * uTime * vec2(0.0002,0.0004);
     vNormal = normalize(normalMatrix * normal);
    //  理论上 y肯定不会小于0 
      // 这个幅度只和uv.x相关，另外搞这个关联，主要是为了避免单调吧，因为现在风向就是x轴  风向可以考虑参数化
      vec2 dir  = uWaveDir* cloudUv.x ;
      float fact  =  abs(dir.x) + abs(dir.y);
      vPosition.xy += wave(5. * fact ,0.3,0.1)*uWaveDir;
     // 优化 这个渐变色可以在顶点着色器中计算。
     vGrassColor= grassColor* uv.y* texture2D(textures[1],cloudUv).rgb*1.5;
     vCloudColor =  texture2D(textures[0],vCloudUv).rgb;
     gl_Position = projectionMatrix * modelViewMatrix * vec4(vPosition, 1.0);

   }
   `;
  fragmentShader = /*glsl */ `
   uniform float uTime;
   uniform vec2 uOffset;
   uniform sampler2D uCloud;
   varying vec2 vUv;
   varying vec3 vNormal;
   varying vec3 vPosition;
   varying vec3 vGrassColor ;
   varying vec3 vCloudColor ;
   void main() {
     // 光照计算
     vec3 lightDirection = vec3(0.6);
     float lightness = abs(dot(vNormal, lightDirection))+0.1; 
     gl_FragColor = vec4(mix(vGrassColor,vCloudColor,.4) ,1.);
    vec3 uvColor = vec3(vUv.x,0.5,vUv.y);
   }
   `;
  constructor(size = 10) {
    super();
    this.uniforms.uRange.value = [2 * size, 2 * size];
  }
}
class Grass extends Mesh {
  declare material: GrassMaterial;
  /**
   *
   * @param size 分布半径
   * @param count 草的数量
   */
  constructor(size = 1, count = 100) {
    const geometry = new GrassGeometry(size, count);
    const material = new GrassMaterial(size);
    super(geometry, material);
  }
  update(time) {
    this.material.uniforms.uTime.value = time;
  }
}

/**
 * 试试不传顶点数据 只使用索引
 */
class GrassBlade extends Mesh {
  frustumCulled = false;
  constructor() {
    const geo = new BufferGeometry();
    // geo.setAttribute('position',new Float32BufferAttribute(new Array(20*3).fill(0),3));
    const indices = [],
      ind = [];
    for (let i = 0; i < 18; i++) {
      indices.push(i, i + 1, i + 2);
      ind.push(i);
    }
   
    // n 个叶片
    const n = 1000000;
    for (let i = 1; i < n; i++) {
      for (let j = 0; j < 18; j++) {
        let offset = 20 * i;
        indices.push(
          indices[j*3] + offset,
          indices[j*3 + 1] + offset,
          indices[j*3 + 2] + offset
        );
      }
    }

    geo.setIndex(indices);
    const mat = new ShaderMaterial({
      vertexShader: /*glsl */ `
      varying vec3 color ;
      vec3 rotateY (float angle, in vec3 pos ){
        float c = cos(angle);
        float s = sin(angle);
        pos.xz+= vec2(s,-c)*pos.x;
        return pos;
      }
      void main() {
        int index = gl_VertexID % 20;
        float y =  float ( index >> 1)/9.;
        float x =  float ( index & 1 )-0.5;
        float f= 1.0 - y*y;
        int bladeIndex = gl_VertexID / 20  ; // 根据叶片的索引计算 其位置
        int size = 1000;
        float col = float(bladeIndex / size - size/2);
        float row = float(bladeIndex % size -size/2) ;
        vec3 posOffset = vec3(col,0.,row)* 0.2; // 偏移量
        // 还需要一个旋转量 
        vec3 pos = vec3(x*f*0.1,y,0.);
        pos = rotateY(sin (float(bladeIndex)*0.13),pos);
        pos += posOffset;
        gl_Position = projectionMatrix * viewMatrix * vec4(pos, 1.0);
        color = vec3(0.1,0.5,0.3)* (y+0.1);
      }  
      `,
      fragmentShader: /*glsl */ `
      varying vec3 color ;
      void main() {
        gl_FragColor = vec4(color,1.);
      }
      `,
      uniforms: {
        uTime: { value: 0 },
      },
      side: DoubleSide,
      wireframe: false,
    });

    super(geo, mat);
  }
}

export function initGrass(
  camera: PerspectiveCamera,
  renderer: WebGLRenderer,
  scene: Scene,
  controls: OrbitControls
) {
  camera.setFocalLength(20);
  controls.update();
  controls.enableDamping = true;
  controls.dampingFactor = 0.05;
  controls.screenSpacePanning = false;
  controls.maxPolarAngle = PI * 0.48; // radians

  const imageLoader = new TextureLoader();
  const cloudTexture = imageLoader.load("/texture/cloud.jpg", (t) => {
    t.wrapS = RepeatWrapping;
    t.wrapT = RepeatWrapping;
  });
  const grassTexture = imageLoader.load("/texture/grass.jpg", (t) => {
    t.wrapS = RepeatWrapping;
    t.wrapT = RepeatWrapping;
  });
  // 百万草叶
  // const grass0 = new InstancedGrass(30, 100000); //16 fps 左右 砍掉了一个yaw 一下子上升到19左右了
  const grass = new Grass(30, 1000000); // 18 fps 左右
  grass.material.uniforms.textures.value = [cloudTexture, grassTexture];

  // const grassBlade = new GrassBlade();
  /*
  const count = 500000,radius = 40;// 这就达到了千万顶点，800万面 
  const instancedGrass  = new InstancedMesh(grassBlade.geometry,grassBlade.material,count);
  const mat4 = new Matrix4(),position = new Vector3(),scale = new Vector3(),q1 = new Quaternion();
  let theta,x,z,c,s,r,Yaxis = new Vector3(0,1,0) ;
  for (let index = 0; index <count; index++) {
    theta = random() * PI * 2;
    r = radius * random();
    c = cos(theta);
    s = sin(theta);
    x = c *r;
    z = s *r;
    position.set(x,0,z);
    scale.set(1,random()*0.5+0.8,1);
    q1.setFromAxisAngle(Yaxis,random()*PI*2);
    mat4.compose(position,q1,scale);
    instancedGrass.setMatrixAt(index,mat4);
  } */

  const ground = new Mesh(
    new PlaneGeometry(20, 20),
    new MeshStandardMaterial({ color: 0x99994c, side: DoubleSide })
  );
  ground.rotation.x = -PI * 0.5;

  // grass.material = wireframeMat;

  console.log(grass);

  scene.add(grass, ground);

  let clock = new Clock();
  let time = 0;
  let deltaTime = 0;

  const grassConfig = {
    size: 10,
    count: 10000,
    color: "#33994c",
    dirx: 1,
    diry: 0,
    needShot: false,
    shot() {
      grassConfig.needShot = true;
    },
  };
  function render(t) {
    deltaTime = clock.getDelta();
    time += deltaTime;
    renderer.render(scene, camera);
    grass.update(t);
    controls.update(deltaTime);
    if (grassConfig.needShot) {
      canvasToImage(renderer.domElement, "grass");
      grassConfig.needShot = false;
    }
    requestAnimationFrame(render);
  }

  function initGUI() {
    const gui = new GUI();
    const color = new Color();
    gui.addColor(grassConfig, "color").onChange((v) => {
      color.set(v);
      color.toArray(grass.material.uniforms.grassColor.value);
    });
    gui.add(grassConfig, "dirx", -1, 1, 0.1).onChange((v) => {
      grass.material.uniforms.uWaveDir.value.x = v;
      grass.material.uniforms.uWaveDir.value.normalize();
    });

    gui.add(grassConfig, "diry", -1, 1, 0.1).onChange((v) => {
      grass.material.uniforms.uWaveDir.value.y = v;
      grass.material.uniforms.uWaveDir.value.normalize();
    });
    gui.add(grassConfig, "shot");
    gui.hide();
    document.addEventListener("keydown", (e) => {
      switch (e.key) {
        case "g":
          gui.show();
          break;
        case "c":
          gui.hide();
          break;
      }
    });
  }
  initGUI();

  render(0);
  return;
}

/**
 * 目前的测试结果是 不使用instanceMesh  ,如果干掉cloudUv 在顶点着色器里计算出来，fps反而会下降
 * 如果使用instanceMesh ， 在顶点着色器里计算cloudUv，干掉一个attribute，fps有明显提升。
 */
